#pragma once

#include "evnet/evnet.hpp"
#include <chrono>

namespace evnet {

class queue
{
public:
    typedef event_base* handle_type;

private:

    handle_type handle_;

public:

    queue()
        : handle_(event_base_new())
    {
        if (!handle_)
            throw std::runtime_error("event_base_new");
    }

    queue(const queue&) = delete;
    queue& operator=(const queue&) = delete;

    operator handle_type() const throw()
    {
        return handle_;
    }

    handle_type handle() const throw()
    {
        return handle_;
    }

    const char* methood() const throw()
    {
        return event_base_get_method(handle_);
    }

    ~queue()
    {
        event_base_free(handle_);
    }

    bool dispatch()
    {
        int result = event_base_dispatch(handle_);

        if (result == -1)
            throw std::runtime_error("event_base_dispatch");

        return result == 0;
    }

    bool loop_loop()
    {
        int result = event_base_loop(handle_, EVLOOP_NONBLOCK);

        if (result == -1)
            throw std::runtime_error("event_base_loop");

        return result == 0;
    }

    void loopexit(const timeval& tv)
    {
        int result = event_base_loopexit(handle_, &tv);

        if (result == -1)
            throw std::runtime_error("loopexit");
    }

    template <typename Duration>
    void loopexit(Duration timeout)
    {
        const auto sec = std::chrono::duration_cast<std::chrono::seconds>(timeout);
        const auto usec = std::chrono::duration_cast<
            std::chrono::microseconds>(timeout) - sec;
        const timeval tv = { static_cast<decltype(tv.tv_sec)>(sec.count()),
            static_cast<decltype(tv.tv_usec)>(usec.count()) };
        loopexit(tv);
    }

    bool loop_once() // block untill no events, then run olny one
    {
        int result = event_base_loop(handle_, EVLOOP_ONCE);

        if (result == -1)
            throw std::runtime_error("event_base_loop");

        return result == 0;
    }

    void loop_break()
    {
        int result = event_base_loopbreak(handle_);
        if (result == -1)
            throw std::runtime_error("event_base_loopbreak");
    }

    bool stopped() const throw()
    {
        return event_base_got_break(handle_)!=0;
    }

    void priority_init(int level)
    {
        int result = event_base_priority_init(handle_, level);
        if (result == -1)
            throw std::runtime_error("event_base_loopbreak");
    }

    template <typename Handler, typename Duration>
    void post(Handler& handler, Duration timeout)
    {
        const auto sec = std::chrono::duration_cast<
            std::chrono::seconds>(timeout);
        const auto usec = std::chrono::duration_cast<
            std::chrono::microseconds>(timeout) - sec;
        const timeval tv = {sec.count(), usec.count()};
        int result = event_base_once(handle_, -1, EV_TIMEOUT,
                &call_handler<Handler>::apply, &handler, &tv);
        if (result != 0)
            throw std::runtime_error("event_base_once");
    }

    template <typename Operation>
    void enqueue(Operation& op)
    {
        op.start(handle_);
    }

private:

    template <typename Handler>
    struct call_handler
    {
        static void apply(evutil_socket_t, short, void* handler)
        {
            try
            {
                *static_cast<Handler*>(handler)();
            }
            catch (const std::exception& e)
            {   }
        }
    };
};

} // namespace evnet
